﻿using Microsoft.VisualStudio.Shell;
using Sasa.QualityTools.CodeAnalysis.Common;
using Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.EventHandlers;
using Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.UI;
using Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.ViewModels;
using Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.VisualStudio;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.Utilities;
using Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.Models;
using System.Windows.Forms;

namespace Sasa.QualityTools.CodeAnalysis.FxCopIntegrator
{
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [ProvideAutoLoad(Constants.UIContext.SolutionExists)]
    [InstalledProductRegistration("#110", "#112", "1.3.0p1 RTW", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [ProvideToolWindow(typeof(CodeAnalysisResultWindowPane), Style = VsDockStyle.MDI, Height = 400, Width = 800, Transient = true)]
    [ProvideToolWindowVisibility(typeof(CodeAnalysisResultWindowPane), Constants.UIContext.SolutionExists)]
    [ProvideToolWindow(typeof(RuleDetailWindowPane), Orientation = ToolWindowOrientation.Right, Style = VsDockStyle.Tabbed, Window = Constants.Window.SolutionExplorer, Transient = true)]
    [ProvideToolWindowVisibility(typeof(RuleDetailWindowPane), Constants.UIContext.SolutionExists)]
    [ProvideOptionPageAttribute(typeof(GeneralOptionPageData), "FxCop Integrator", "General", 1001, 1002, true)]
    [ProvideProfileAttribute(typeof(GeneralOptionPageData), "FxCop Integrator", "General", 1001, 1002, true, DescriptionResourceID = 1003)]
    [Guid(Constants.PackageId)]
    public sealed class FxCopIntegratorPackage : Package
    {
        public GeneralOption GeneralOption
        {
            get
            {
                return new GeneralOption((GeneralOptionPageData)GetDialogPage(typeof(GeneralOptionPageData)));
            }
        }


        protected override void Initialize()
        {
            Logger.Register(new ActivityLogger());

            Logger.Info("Began package initialization.");

            base.Initialize();

            var mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (mcs != null)
            {
                var commandSetGuid = new Guid(Constants.CommandSetId);
                mcs.AddCommand(new MenuCommand(PerformSolutionAnalysis, new CommandID(commandSetGuid, 0x0100)));
                mcs.AddCommand(new MenuCommand(PerformProjectAnalysis, new CommandID(commandSetGuid, 0x0101)));
                mcs.AddCommand(new MenuCommand(ShowCodeAnalysisResultWindow, new CommandID(commandSetGuid, 0x0102)));
                mcs.AddCommand(new MenuCommand(EditSolutionConfiguration, new CommandID(commandSetGuid, 0x0108)));
                mcs.AddCommand(new MenuCommand(EditProjectConfiguration, new CommandID(commandSetGuid, 0x0110)));
                mcs.AddCommand(new MenuCommand(EditFxCopDictionary, new CommandID(commandSetGuid, 0x0111)));

                // Disable to load report because this feature is not well-tested.
                //mcs.AddCommand(new MenuCommand(LoadReport, new CommandID(commandSetGuid, 0x0104)));
            }

            Logger.Info("Ended package initialization.");
        }

        private void PerformSolutionAnalysis(object sender, EventArgs e)
        {
            Logger.Info("Began code analysis of entire solution.");

            try
            {
                var ide = new Ide(this);
                var solution = ide.Solution;

                if (!solution.Build())
                {
                    ide.ShowErrorListPane();
                    Logger.Warn("Ended code analysis of entire solution because build failed.");
                    return;
                }

                var viewModel = new CodeAnalysisProgressViewModel();
                if (solution.HasFxCopProjectFile)
                {
                    viewModel.Analyses.Add(CreateCodeAnalysisViewModel(solution));
                }
                else
                {
                    foreach (var project in solution.GetBuildTargetProjects(true))
                    {
                        viewModel.Analyses.Add(CreateCodeAnalysisViewModel(project));
                    }
                }

                PerformCodeAnalysis(viewModel);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to analyze code because: ");
                throw;
            }

            Logger.Info("Ended code analysis of entire solution.");
        }

        private void PerformProjectAnalysis(object sender, EventArgs e)
        {
            Logger.Info("Began code analysis of the specific project.");

            try
            {
                var ide = new Ide(this);
                var project = ide.Solution.SelectedProject;

                if (!project.Build())
                {
                    ide.ShowErrorListPane();
                    Logger.Warn("Ended code analysis of the specific project because build failed.");
                    return;
                }

                var viewModel = new CodeAnalysisProgressViewModel();
                viewModel.Analyses.Add(CreateCodeAnalysisViewModel(project));

                PerformCodeAnalysis(viewModel);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to analyze code because: ");
                throw;
            }

            Logger.Info("Ended code analysis of the specific project.");
        }

        private CodeAnalysisViewModel CreateCodeAnalysisViewModel(Solution solution)
        {
            var viewModel = new CodeAnalysisViewModel();

            var options = (GeneralOptionPageData)GetDialogPage(typeof(GeneralOptionPageData));
            viewModel.FxCopCmdPath = options.FxCopCmdPath;
            viewModel.FxCopCmdOption = FxCopCmdOptionBuilder.Build(solution);
            viewModel.Target = "FxCop Project File";

            return viewModel;
        }

        private CodeAnalysisViewModel CreateCodeAnalysisViewModel(Project project)
        {
            var viewModel = new CodeAnalysisViewModel();
            viewModel.Target = project.Name;

            if (!project.ShouldAnalyze)
            {
                viewModel.Status = CodeAnalysisStatus.Skip;
                viewModel.Message = "FxCop Integrator doesn't support to analyze this project.";
            }
            else
            {
                var options = (GeneralOptionPageData)GetDialogPage(typeof(GeneralOptionPageData));
                viewModel.FxCopCmdPath = options.FxCopCmdPath;
                viewModel.FxCopCmdOption = FxCopCmdOptionBuilder.Build(project, GeneralOption.FxCopVersion);

                var config = CodeAnalysisConfiguration.Load(project.ConfigurationPath);
                config.Override(CodeAnalysisConfiguration.Load(project.Solution.ConfigurationPath));
                if (!config.EnableCodeAnalysis)
                {
                    viewModel.Status = CodeAnalysisStatus.Skip;
                    viewModel.Message = "Code analysis is disabled.";
                }
            }

            return viewModel;
        }

        private void PerformCodeAnalysis(CodeAnalysisProgressViewModel viewModel)
        {
            if (viewModel.Analyses.Where(o => o.Status != CodeAnalysisStatus.Skip).Count() == 0)
            {
                Logger.Info("FxCop Integrator didn't show code analysis progress window because it couldn't find a target that should be analyzed.");
                return;
            }

            viewModel.FxCopVersion = GeneralOption.FxCopVersion;

            var window = new CodeAnalysisProgressWindow();
            window.DataContext = viewModel;
            var success = window.ShowModal();

            if (!(success.HasValue && success.Value == true))
            {
                return;
            }

            var ide = new Ide(this);

            var mapping=  new Dictionary<string, Project>();
            foreach (var prj in ide.Solution.GetBuildTargetProjects(false))
            {
                if (prj.ShouldAnalyze)
                {
                    mapping[prj.AssemblyPath] = prj;
                }
            }

            var result = CreateCodeAnalysisResultViewModel();
            foreach (var problem in viewModel.Problems)
            {
                if (problem.AssemblyPath != null)
                {
                    var prj = mapping[problem.AssemblyPath];
                    problem.ProjectName = prj.Name;
                    if (problem.Path == null && problem.TypeName != null)
                    {
                        problem.Path = prj.GetFilePathFromTypeName(problem.TypeName);
                    }
                }
                result.Problems.Add(problem);
            }
            foreach (var rule in viewModel.Rules)
            {
                result.Rules[rule.CheckId] = rule;
            }

            var resultPane = ide.ShowToolWindowPane<CodeAnalysisResultWindowPane>();
            ((CodeAnalysisResultContent)resultPane.Content).DataContext = result;

            result.GoToFirstPage();
        }

        private CodeAnalysisResultViewModel CreateCodeAnalysisResultViewModel()
        {
            var viewModel = new CodeAnalysisResultViewModel();
            var ide = new Ide(this);

            viewModel.CodeFocusRequested += (sender, e) =>
            {
                if (e.Problem.Path != null)
                {
                    ide.Solution.Open(e.Problem.Path, e.Problem.Line);
                }
            };

            viewModel.ShowRuleRequired += (sender, e) =>
            {
                var rulePane = ide.ShowToolWindowPane<RuleDetailWindowPane>();
                ((RuleDetailContent)rulePane.Content).DataContext = e.Rule;
            };

            return viewModel;
        }

        private void ShowCodeAnalysisResultWindow(object sender, EventArgs e)
        {
            try
            {
                var ide = new Ide(this);
                ide.ShowToolWindowPane<CodeAnalysisResultWindowPane>();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to show code analysis result window because: ");
                throw;
            }
        }

        private void EditSolutionConfiguration(object sender, EventArgs e)
        {
            Logger.Info("Began to edit code analysis configuration of solution.");

            try
            {
                var ide = new Ide(this);
                var solution = ide.Solution;

                var config = CodeAnalysisConfiguration.Load(solution.ConfigurationPath);
                var viewModel = new CodeAnalysisConfigurationViewModel(config);

                var directory = Path.GetDirectoryName(solution.ConfigurationPath);
                foreach (var ruleSet in FindRuleSets(directory))
                {
                    viewModel.RuleSets.Add(ruleSet);
                }

                viewModel.BrowseRuleSetRequired += delegate
                {
                    var dialog = new OpenFileDialog();
                    dialog.Title = "Select RuleSet";
                    dialog.DefaultExt = "ruleset";
                    dialog.Filter = "RuleSet (*.ruleset)|*ruleset";
                    if (dialog.ShowDialog() == DialogResult.OK)
                    {
                        var path = Path.Combine(Path.GetDirectoryName(config.ConfigurationPath), Path.GetFileName(dialog.FileName));
                        File.Copy(dialog.FileName, path, true);
                        solution.AddSolutionFolder("CodeAnalysis").AddFromFile(path);
                        var ruleSet = RuleSet.CreateInstance(path);
                        viewModel.RuleSets.Add(new RuleSetViewModel(ruleSet));
                        viewModel.RuleSet = ruleSet.Path;
                    }
                };

                var window = new SolutionConfigurationEditorWindow();
                window.DataContext = viewModel;
                viewModel.Saved += delegate
                {
                    solution.AddSolutionFolder("CodeAnalysis").AddFromFile(config.ConfigurationPath);
                };
                viewModel.CloseRequired += delegate
                {
                    window.Close();
                };

                window.ShowModal();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to show code analysis result window because: ");
                throw;
            }
        }

        private void EditProjectConfiguration(object sender, EventArgs e)
        {
            Logger.Info("Began to edit code analysis configuration of project.");

            try
            {
                var ide = new Ide(this);
                var solution = ide.Solution;
                var project = solution.SelectedProject;

                var config = CodeAnalysisConfiguration.Load(project.ConfigurationPath);
                var viewModel = new CodeAnalysisConfigurationViewModel(config);

                var directory = Path.GetDirectoryName(project.ConfigurationPath);
                foreach (var ruleSet in FindRuleSets(directory))
                {
                    viewModel.RuleSets.Add(ruleSet);
                }

                viewModel.BrowseRuleSetRequired += delegate
                {
                    var dialog = new OpenFileDialog();
                    dialog.Title = "Select RuleSet";
                    dialog.DefaultExt = "ruleset";
                    dialog.Filter = "RuleSet (*.ruleset)|*ruleset";
                    if (dialog.ShowDialog() == DialogResult.OK)
                    {
                        var path = Path.Combine(Path.GetDirectoryName(config.ConfigurationPath), Path.GetFileName(dialog.FileName));
                        File.Copy(dialog.FileName, path, true);
                        project.AddFromDirectory(directory).AddFromFile(path);
                        var ruleSet = RuleSet.CreateInstance(path);
                        viewModel.RuleSets.Add(new RuleSetViewModel(ruleSet));
                        viewModel.RuleSet = ruleSet.Path;
                    }
                };

                var window = new CodeAnalysisConfigurationEditorWindow();
                window.DataContext = viewModel;
                viewModel.Saved += delegate
                {
                    project.AddFromDirectory(directory).AddFromFile(config.ConfigurationPath);
                    project.Save();
                };
                viewModel.CloseRequired += delegate
                {
                    window.Close();
                };

                window.ShowModal();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to show code analysis result window because: ");
                throw;
            }
        }

        private void EditFxCopDictionary(object sender, EventArgs e)
        {
            Logger.Info("Edit FxCop dictionary");

            try
            {
                var ide = new Ide(this);
                var solution = ide.Solution;
                var model = FxCopDictionary.Load(solution.FxCopDictionaryPath);
                var viewModel = new FxCopDictionaryViewModel(model);

                var view = new FxCopDictionaryEditorWindow();
                view.DataContext = viewModel;
                viewModel.Saved += delegate
                {
                    solution.AddSolutionFolder("CodeAnalysis").AddFromFile(solution.FxCopDictionaryPath);
                };
                viewModel.CloseRequired += delegate { view.Close(); };


                view.ShowModal();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to edit FxCop dictionary.");
                throw;
            }
        }

        private IEnumerable<RuleSetViewModel> FindRuleSets(string customRuleSetDirectory)
        {
            yield return new RuleSetViewModel { Name = "<None>" };

            var bundleRuleSetDirectory = Path.Combine(Environment.GetEnvironmentVariable("VS100COMNTOOLS"), "..", "..", "Team Tools", "Static Analysis Tools", "Rule Sets");
            foreach (var ruleSet in RuleSet.Load(bundleRuleSetDirectory))
            {
                yield return new RuleSetViewModel(ruleSet);
            }

            foreach (var ruleSet in RuleSet.Load(customRuleSetDirectory))
            {
                yield return new RuleSetViewModel(ruleSet);
            }
        }
    }


    public class GeneralOption
    {
        private GeneralOptionPageData optionPage;


        public string FxCopCmdPath
        {
            get
            {
                return optionPage.FxCopCmdPath;
            }
        }

        public Version FxCopVersion
        {
            get
            {
                try
                {
                    return System.Reflection.Assembly.LoadFile(optionPage.FxCopCmdPath).GetName().Version;
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, "Failed to load FxCopCmd from '{0}'.", optionPage.FxCopCmdPath);
                    return new Version();
                }
            }
        }


        public GeneralOption(GeneralOptionPageData optionPage)
        {
            this.optionPage = optionPage;
        }
    }
}
